import Rational from "./rational";
import Decimal from "./decimal"
import Fraction from "./fraction"
import getFractor from "../../algorithm/get-fractor";
import primeFactorization from "../../algorithm/prime-factorization";
import store from "../../store";
import isPrime from "../../algorithm/is-prime";

/**
 * @type {BigInt[]}
 */
store.primes = [2n]

/**
 * @type {Object.<string, BigInt[]>}
 */
store.fractors = { '1': [1n] }

store.primeFractors = {'2':[2n]}

class Int extends Rational {

    toString() {
        return this.#value.toString()
    }

    get [Symbol.toStringTag]() {
        return 'Int '+this.value
    }
    /**
     * @type {BigInt}
     */
    #value = 0n

    /**
     * @type {Rational|null}
     */
    #reciprocal = null

    /**
     * @type {Fraction|null}
     */
    #fraction = null

    /**
     * @type {boolean|undefined}
     */
    #isPrime = undefined

    /**
     * 
     * @param {BigInt} value 
     */
    constructor(value) {
        super("int")
        if (typeof value !== "bigint")
            throw new Error('Value must be a BigInt')
        this.#value = value;
    }

    isPrime() {
        if (this.#isPrime !== undefined)
            return this.#isPrime
        if (this.#value === 1n)
            return this.#isPrime = false
        if (this.#value === 2n)
            return this.#isPrime = true
        return this.#isPrime = isPrime(this.#value)
    }

    toFraction() {
        if (this.#fraction == null)
            this.#fraction = new Fraction(this, new Int(1n))
        return this.#fraction
    }

    toDecimal() {
        return new Decimal(this.#value, '0')
    }

    /**
     * 
     * @param {Rational} value 
     * @returns 
     */
    add(value) {
        if (!Rational.is(value))
            throw new Error('Value must be a Rational')
        if (value instanceof Int)
            return new Int(this.#value + value.value)
        if (value instanceof Fraction)
            return new Fraction(this.mul(value.value.denominator).add(value.value.numerator), value.value.denominator)
    }

    sub(value) {
        if (!Rational.is(value))
            throw new Error('Value must be a Rational')
        return this.add(value.mul(new Int(-1n)))
    }

    mul(value) {
        if (!Rational.is(value))
            throw new Error('Value must be a Rational')
        if (value instanceof Int)
            return new Int(this.#value * value.value)
        if (value instanceof Fraction)
            return new Fraction(this.mul(value.value.numerator), value.value.denominator)
    }

    compare(value) {
        if (!Rational.is(value))
            throw new Error('Value must be a Rational')
        return this.toFraction().compare(value.toFraction())
    }

    /**
     * 
     * @param {Rational} value 
     * @returns {Fraction|Int}
     */
    div(value) {
        if (!Rational.is(value))
            throw new Error('Value must be a Rational')
        return new Fraction(this, value).simplify()
    }

    /**
     * @returns {BigInt[]}
     */
    static get primeFractors() {
        return store.primeFractors
    }

    /**
     * 
     * @param {string} num 
     * @param {BigInt[]} fractors 
     * @returns 
     */
    static setPrimeFractor(num, fractors) {
        if (!Int.primeFractors[num])
            return store.primeFractors[num] = fractors
        return false
    }

    /**
     * 
     * @param {string} num 
     * @param {BigInt[]} fractors 
     * @returns 
     */
    static setFractor(num, fractors) {
        if (!Int.fractors[num])
            return store.fractors[num] = fractors
        return false
    }

    /**
    * @returns {Object.<string, BigInt[]>}
    */
    static get fractors() {
        return store.fractors
    }

    /**
     * @returns {BigInt[]}
     */
    static get primes() {
        return store.primes
    }

    /**
     * @param {BigInt} prime
     */
    static set prime (prime) {
        if (!Int.primes.includes(prime))
            return store.primes.push(prime)
        return false
    }

    get fractors() {
        return getFractor(this.#value).map(x => new Int(x))
    }

    get primeFractors() {
        return primeFactorization(this.#value).map(x => new Int(x))
    }

    /**
     * @returns {BigInt}
     */
    get value () {
        return this.#value
    }

    get reciprocal() {
        if (!this.#reciprocal) {
            this.#reciprocal = new Fraction(1, this.#value).simplify()
        }
        return this.#reciprocal
    }
}

export default Int